/*
 * Copyright (C) 2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the 'License');
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an 'AS IS' BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef IMAGE_KNIFE_C_KEKE_TRANSFORMATION_BASE_H
#define IMAGE_KNIFE_C_KEKE_TRANSFORMATION_BASE_H

#include "imageknife_request_internal.h"
#include "imageknife_task_internal.h"
#include "transformation/transformation.h"
#include "utils/log.h"
#include "securec.h"

namespace ImageKnifePro {

class TransformationBase : public Transformation {
public:
    bool Transform(std::shared_ptr<ImageKnifeTask> task) final
    {
        IMAGE_KNIFE_CHECK_RETURN(task == nullptr, false);
        if (task->product.imageData == nullptr) {
            task->EchoError("task->product.imageData == Nullptr");
            return false;
        }

        EndCallback callback;
        callback.func = [this]() {this->AfterProcess();};
        if (!BeforeProcess(task)) {
            return false;
        }

        uint32_t count = task->product.imageData->GetFrameCount();
        if (count > 1) {
            return TransformAnimation(task, count);
        } else if (count <= 0) {
            task->EchoError("task->product.imageData->GetFrameCount <= 0");
            return false;
        } else {
            OH_PixelmapNative *pixelmapIn = task->product.imageData->GetPixelmap();
            if (pixelmapIn == nullptr) {
                task->product.imageData = nullptr;
                task->EchoError("task->product.imageData->GetPixelmap() == nullptr");
                return false;
            }
            OH_PixelmapNative *pixelmapOut = nullptr;
            std::string errorInfo = Process(pixelmapIn, &pixelmapOut);
            if (!errorInfo.empty()) {
                task->EchoError(errorInfo);
                return false;
            }
            if (pixelmapOut != nullptr) {
                // 修改不在原图上进行，替换原有ImageData
                task->product.imageData = std::make_shared<ImageData>(pixelmapOut);
            }
        }
        return true;
    }
protected:
    struct PixemapInfo {
        uint8_t *innerBuffer = nullptr;
        size_t bufferLength = 0;
        uint32_t width = 0;
        uint32_t height = 0;
        int32_t format = PIXEL_FORMAT_UNKNOWN;
    };

    /**
     * @brief override 该函数来处理图片变换前的业务，该函数在本次图形变换中只会执行一次
     * @param task 加载任务对象
     * @return 返回false图形变换将结束，返回true继续进行图形变换
     */
    virtual bool BeforeProcess(std::shared_ptr<ImageKnifeTask> task)
    {
        return true;
    }

    /**
     * @brief override 该函数来处理图片变换后的业务，该函数在本次图形变换中只会执行一次
     */
    virtual void AfterProcess()
    {
    }

    /**
     * @brief override 该函数来处理单帧pixelmap的图形变换
     * @param pixelmapIn 需要处理的图片，不能为nullptr
     * @param pixelmapOut 处理后的图片副本，如果pixelmapOut写回nullptr，代表图形变换在原图pixelmapIn进行
     * @return 返回错误信息，为空字符串代表图形变换成功，没有错误
     */
    virtual std::string Process(OH_PixelmapNative* pixelmapIn, OH_PixelmapNative **pixelmapOut) = 0;

    PixemapInfo ParsePixelmapInfoReadPixels(OH_PixelmapNative *pixelmap, bool readPixels = true)
    {
        const int number4 = 4;
        const int number2 = 2;
        PixemapInfo info;
        IMAGE_KNIFE_CHECK_RETURN(pixelmap == nullptr, info);
        OH_Pixelmap_ImageInfo *imageInfo = nullptr;

        Image_ErrorCode errorCode = OH_PixelmapImageInfo_Create(&imageInfo);
        if (errorCode != IMAGE_SUCCESS) {
            IMAGE_KNIFE_LOG(LOG_ERROR, "Parse Pixelmap Create ImageInfo Failed: %{public}d", errorCode);
            return info;
        }
        errorCode = OH_PixelmapNative_GetImageInfo(pixelmap, imageInfo);
        if (errorCode != IMAGE_SUCCESS) {
            IMAGE_KNIFE_LOG(LOG_ERROR, "Parse Pixelmap Get ImageInfo Failed: %{public}d", errorCode);
            OH_PixelmapImageInfo_Release(imageInfo);
            return info;
        }

        OH_PixelmapImageInfo_GetWidth(imageInfo, &info.width);
        OH_PixelmapImageInfo_GetHeight(imageInfo, &info.height);

        OH_PixelmapImageInfo_GetPixelFormat(imageInfo, &info.format);
        OH_PixelmapImageInfo_Release(imageInfo);
        IMAGE_KNIFE_CHECK_RETURN(info.width <= 0 || info.height <= 0, info);
        IMAGE_KNIFE_CHECK_RETURN(info.format != PIXEL_FORMAT_RGBA_8888, info,
                                 "Default Transformation Only Support RGBA For Now");
        IMAGE_KNIFE_CHECK_RETURN(info.format == PIXEL_FORMAT_UNKNOWN, info);

        info.bufferLength = info.width * info.height * number4;
        if (info.format == PIXEL_FORMAT_NV21 || info.format == PIXEL_FORMAT_NV12) {
            info.bufferLength = info.width * info.height + ((info.width + 1) / number2) *
                                ((info.height + 1) / number2) * number2;
        }

        if (readPixels) {
            info.innerBuffer = (uint8_t *)malloc(info.bufferLength);
            errorCode = OH_PixelmapNative_ReadPixels(pixelmap, info.innerBuffer, &info.bufferLength);
            if (errorCode != IMAGE_SUCCESS) {
                free(info.innerBuffer);
                info.innerBuffer = nullptr;
            }
        }
        return info;
    }

    void CreatePixelmapWithBuffer(uint8_t *buffer, PixemapInfo info, OH_PixelmapNative **pixelmap)
    {
        OH_Pixelmap_InitializationOptions *options = nullptr;
        Image_ErrorCode errorCode = OH_PixelmapInitializationOptions_Create(&options);
        if (errorCode != IMAGE_SUCCESS) {
            IMAGE_KNIFE_LOG(LOG_ERROR, "Create Pixelmap Initialization Options Failed:%{public}d", errorCode);
            return;
        }
        OH_PixelmapInitializationOptions_SetWidth(options, info.width);
        OH_PixelmapInitializationOptions_SetHeight(options, info.height);
        OH_PixelmapInitializationOptions_SetPixelFormat(options, info.format);
        OH_PixelmapInitializationOptions_SetSrcPixelFormat(options, info.format);

        errorCode = OH_PixelmapNative_CreatePixelmap(buffer, info.bufferLength, options, pixelmap);
        if (errorCode != IMAGE_SUCCESS) {
            IMAGE_KNIFE_LOG(LOG_ERROR, "Create Pixelmap Failed:%{public}d", errorCode);
        }
        OH_PixelmapInitializationOptions_Release(options);
    }

private:
    struct EndCallback {
        std::function<void()> func = nullptr;
        ~EndCallback()
        {
            if (func != nullptr) {
                func();
            }
        }
    };

    bool TransformAnimation(std::shared_ptr<ImageKnifeTask> task, uint32_t count)
    {
        auto taskInternal = std::dynamic_pointer_cast<ImageKnifeTaskInternal>(task);
        IMAGE_KNIFE_CHECK_RETURN(taskInternal == nullptr, false);
        auto request = taskInternal->GetImageKnifeRequestInternal();
        IMAGE_KNIFE_CHECK_RETURN(request == nullptr, false);

        OH_PixelmapNative **pixelmapListIn = task->product.imageData->GetPixelmapList();
        int *timeListIn = task->product.imageData->GetDelayTimeList();
        if (pixelmapListIn == nullptr) {
            task->EchoError("task->product.imageData->GetPixelmapList() == nullptr Animator");
            return false;
        }
        if (timeListIn == nullptr) {
            task->EchoError("task->product.imageData->GetDelayTimeList() == nullptr Animator");
            return false;
        }

        IMAGE_KNIFE_CHECK_RETURN(count <= 0, false);
        OH_PixelmapNative **pixelmapListOut = new OH_PixelmapNative *[count];
        int *timeListOut = new int [count];
        (void)memset_s(pixelmapListOut, sizeof(OH_PixelmapNative *) * count, 0,
                       sizeof(OH_PixelmapNative *) * count);
        (void)memset_s(timeListOut, sizeof(int) * count, 0, sizeof(int) * count);
        int countOut = 0;
        for (int i = 0; i<count; i++) {
            if (request->IsDestroy() || taskInternal->IsFatalErrorHappened()) {
                IMAGE_KNIFE_LOG(LOG_DEBUG, "%{public}s Transform Animation Abort", taskInternal->GetTaskInfo().c_str());
                break;
            }
            if (pixelmapListIn[i] == nullptr) {
                task->EchoError("pixelmap == nullptr, Frame:" + std::to_string(i));
                continue;
            }

            std::string errorInfo = Process(pixelmapListIn[i], &pixelmapListOut[countOut]);
            if (errorInfo.empty()) {
                // 在原图上buffer上进行的变换
                if (pixelmapListOut[countOut] == nullptr) {
                    pixelmapListOut[countOut] = pixelmapListIn[i];
                    pixelmapListIn[i] = nullptr;
                }
                timeListOut[countOut] = timeListIn[i];
                countOut++;
            } else {
                task->EchoError(errorInfo + ",Frame:" + std::to_string(i));
            }
        }

        if (countOut <= 0) {
            delete [] pixelmapListOut;
            delete [] timeListOut;
            return false;
        }
        task->product.imageData = std::make_shared<ImageData>(pixelmapListOut, timeListOut, countOut);
        return true;
    }
};
}

#endif // IMAGE_KNIFE_C_KEKE_TRANSFORMATION_BASE_H
